package org.qdao;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

import org.qdao.annotation.Column;
import org.qdao.annotation.PrimaryKey;
import org.qdao.annotation.Table;
import org.qdao.core.JarClassLoader;
import org.qdao.implement.mysql.MySqlEntityDao;
import org.qdao.implement.oracle.OracleEntityDao;
/**
 * 
 * @author 譚元吉
 * @since 2010/02/06 11:00:50
 */
public abstract class AbstractDBEngine implements IDBEngine {

	protected DBConfig config;

	private Connection connection;

	private Driver driver;

	private JarClassLoader loader;
	
	private Map<Class<?>, TableDescriptor> tableCache;
	
	protected boolean isLogger;
	
	
	// initialize the properties
	private Properties info = new Properties();
	
	
	protected Logger logger = Logger.getLogger( getClass().getName() );
	
	public AbstractDBEngine() {
		this(DBConfig.getInstance());
	}

	public AbstractDBEngine(DBConfig config) {
		this.config = config;
		isLogger = config.isLogger();
		tableCache = new HashMap<Class<?>, TableDescriptor>();
		tableCache.clear();
		info.setProperty("user", config.username);
		info.setProperty("password", config.password);
	}

	
	public TableDescriptor getTableDescriptor(Class<?> tableClazz) {
		TableDescriptor tableDescriptor;
		tableDescriptor = tableCache.get(tableClazz);
		if (tableDescriptor != null) {
			return tableDescriptor;
		}
		// TODO : use reflect to get annotation from table class
		tableDescriptor = new TableDescriptor();

		Table table = (Table) tableClazz.getAnnotation(Table.class);
		if (table != null) {
			tableDescriptor.name = table.name();
			tableDescriptor.description = table.description();
		}

		Field[] fields = tableClazz.getDeclaredFields();
		List<TableDescriptor.ColumnDescription> columnDescriptors =
				new ArrayList<TableDescriptor.ColumnDescription>();

		List<String> primaryKeys = new ArrayList<String>();
		List<TableDescriptor.ColumnDescription> pks = new ArrayList<TableDescriptor.ColumnDescription>();
		Column co;
		PrimaryKey pk;
		TableDescriptor.ColumnDescription cd;
/*		TableDescriptor.FkDescription fkd;
		String fkTableName;
		ForeignKey fk;
*/		for (int i = 0; i < fields.length; i++) {
			co = fields[i].getAnnotation(Column.class);
			pk = fields[i].getAnnotation(PrimaryKey.class);
//			fk = fields[i].getAnnotation(ForeignKey.class);
			if (co != null) {
				cd = new TableDescriptor.ColumnDescription();
				cd.decimal = co.decimal();
				cd.description = co.description();
				cd.name = co.name();
				cd.length = co.length();
				cd.nullable = co.nullable();
				cd.type = co.type();
				cd.javaField = fields[i].getName();
				cd.primaryKey = null != pk;
				cd.autoIncrement = co.autoIncrement();
				cd.defaultValue = co.defaultValue();
				columnDescriptors.add(cd);
				
				if (cd.primaryKey) {
					pks.add(cd);
				}
				
				/*if (fk != null) {
					table = fk.getClass().getAnnotation(Table.class);
					fkTableName = null == table ? null : table.name();
					if (null != fkTableName) {
						fkd = new TableDescriptor.FkDescription();
						fkd.foreignKeys = "("  + co.name();
						fkd.references = fkTableName + " (";
					}
				}*/
			}

			if (pk != null) {
				primaryKeys.add(co.name());
			}
		}
		// set the columns
		tableDescriptor.columns = columnDescriptors.toArray(
				new TableDescriptor.ColumnDescription[columnDescriptors.size()]);
		tableDescriptor.primaryKeys = primaryKeys.toArray(new String[primaryKeys.size()]);
		tableDescriptor.pks = pks.toArray(new TableDescriptor.ColumnDescription[pks.size()]);
		tableCache.put(tableClazz, tableDescriptor);
		return tableDescriptor;
	}

	@SuppressWarnings("unchecked")
	public InvocationHandler getEntityDaoHandler(Class<?> entity) {
		return getEntityDaoHandler(entity, config.isLogSql());
	}

	@SuppressWarnings("unchecked")
	public InvocationHandler getEntityDaoHandler(Class<?> entity, boolean isLogger) {
		if (null == config || config.driver == null) {
			return new DefaultEntityDao(this, entity, isLogger);
		}
		String driverLowerCase = config.driver.toLowerCase();
		if (driverLowerCase.indexOf("oracle") != -1) {
			return new OracleEntityDao(this, entity, isLogger);
		} else if (driverLowerCase.indexOf("mysql") != -1) {
			return new MySqlEntityDao(this, entity, isLogger);
		}
		return new DefaultEntityDao(this, entity, isLogger);
	}

	
	public IEntityDao<?> getEntityDao(Class<?> entity) throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException,
			InvocationTargetException, NoSuchMethodException {
		InvocationHandler handler = getEntityDaoHandler(entity);
		return (IEntityDao<?>) Proxy.newProxyInstance(IEntityDao.class.getClassLoader(),
						// new Class[] { IEntityDao.class },
				new Class[] { IEntityDao.class, InvocationHandler.class },
				handler);
	}

	
	public IEntityDao<?> getEntityDao(Class<?> entity, boolean isLogger) throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException,
			NoSuchMethodException {
		InvocationHandler handler = getEntityDaoHandler(entity, isLogger);
		return (IEntityDao<?>) Proxy.newProxyInstance(IEntityDao.class.getClassLoader(),
						// new Class[] { IEntityDao.class },
				new Class[] { IEntityDao.class, InvocationHandler.class },
				handler);
	}

	
	public void dispose() {
		try {
			if (connection != null && !connection.isClosed()) {
				connection.close();
				connection = null;
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	
	public Connection getConnection() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException {
		// judge the connection is right
		if (connection == null || connection.isClosed()) {
			// judge the config is right
			if (config == null || !this.support(config)) {
				return null;
			}
			
			// when the driver is not null then connect the url for the db with the propeties "info"
			if ( null == driver ) {
				Class<?> clazz = null;
				
				try {
					clazz = Class.forName( config.driver ); // use the current class loader.
				} catch( Throwable e ) {
				}
				
				if ( null != clazz ) {
					log( " ClassLoader 加载驱动:" + config.driver + " 成功! [WEB-INFO/LIB加载]" );
				} else if ( config.validateJarPath() ){
					// load the class
					clazz = getLoader().loadClass(config.driver);
					log( " JarClassLoader 加载驱动:" + config.driver + " 成功! [JAR文件加载]" );
				}
				driver = (Driver) clazz.newInstance();
			}
			
			// connect to db
			connection = driver.connect(config.url, info);
			
		}
		return connection;
	}

	
	public boolean initialize(DBConfig config) throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException {
		this.config = config;
		return true;
	}
	
	
    public boolean checkConnection()
    {
        Connection conn = null;
        String cataLog;
        boolean result = false;
        try
        {
            conn = getConnection();
            result = (null != conn);
            if (result) {
            	cataLog = conn.getCatalog();
                System.out.println(cataLog);
            }
        }
        catch (SQLException e)
        {
            e.printStackTrace();
        }
        catch (InstantiationException e)
        {
            e.printStackTrace();
        }
        catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }
        catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        } finally {
            if (null != conn) {
                try
                {
                    conn.close();
                }
                catch (SQLException e)
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                conn = null;
            }
        }
        
        return result;
    }

	// getter
	public DBConfig getConfig() {
		return config;
	}

	private JarClassLoader getLoader() {
		if (loader == null) {
			loader = new JarClassLoader(config.jarPath, this.getClass().getClassLoader());
		}
		return this.loader;
	}

	public Driver getDriver() {
		return this.driver;
	}
	
	public void log( final Object msg ) {
		if ( isLogger ) {
			logger.info( String.valueOf(msg) );
		}
	}
}
